package cloud.agileframework.common.util.collection;

import cloud.agileframework.common.constant.Constant;
import cloud.agileframework.common.util.clazz.ClassUtil;
import cloud.agileframework.common.util.object.ObjectUtil;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author 佟盟
 * @version 1.0
 * @since 1.0
 */
public class TreeUtil {

    /**
     * 快速构建树形结构
     * <p>
     * 借助引用，快速构建树形结构，避免了递归的消耗
     *
     * @param nodes 构建源数据
     * @return 树形结构数据集
     */
    public static Set<TreeBase> createTree(Collection<? extends TreeBase> nodes, String rootValue, String splitChar, Set<Field> fullFieldSet) {
        Map<Optional<String>, List<TreeBase>> children = nodes.stream().filter(node -> {
            node.getChildren().clear();
            return !Objects.equals(node.getParentId(), rootValue);
        }).collect(Collectors.groupingBy(n -> Optional.ofNullable(n.getParentId())));

        nodes.forEach(node -> {
            final List<TreeBase> child = children.get(Optional.ofNullable(node.getId()));
            if (child == null) {
                return;
            }
            node.setChildren(new ConcurrentSkipListSet<>(child));
        });

        //计算full属性值，借助ParentWrapper包裹，通过引用实现快速计算
        if (fullFieldSet != null && !fullFieldSet.isEmpty()) {
            List<ParentWrapper> wrapperList = nodes.stream().map(ParentWrapper::new).collect(Collectors.toList());
            Map<String, ParentWrapper> map = Maps.newConcurrentMap();
            wrapperList.forEach(wrapper -> map.put(wrapper.getCurrent().getId(), wrapper));
            wrapperList.forEach(wrapper -> {
                Object parentId = wrapper.getCurrent().getParentId();
                if (parentId == null) {
                    return;
                }
                ParentWrapper parentWrapper = map.get(parentId);
                if (parentWrapper == null) {
                    return;
                }
                wrapper.setParent(parentWrapper);
            });

            Map<Object, TreeBase> cache = Maps.newConcurrentMap();
            fullFieldSet.forEach(b -> {
                wrapperList.forEach(wrapper -> {
                    Object v = wrapper.getFull(b, splitChar);
                    if (v == null) {
                        return;
                    }
                    cache.put(v, wrapper.getCurrent());
                });
                cache.forEach((key, value) -> ObjectUtil.setValue(value, b, key));
                cache.clear();
            });
        }
        return nodes.stream().filter(node -> Objects.equals(node.getParentId(), rootValue)).collect(Collectors.toCollection(ConcurrentSkipListSet::new));
    }

    public static Set<TreeBase> createTree(Collection<? extends TreeBase> list, String rootValue, String splitChar, String... fullFields) {
        if (!list.isEmpty()) {
            Optional<TreeBase> found = Optional.empty();
            for (TreeBase treeBase : list) {
                found = Optional.of(treeBase);
                break;
            }
            TreeBase entity = found.orElseThrow(IllegalArgumentException::new);
            Class<? extends TreeBase> tClass = entity.getClass();

            Set<Field> fullFieldSet = Stream.of(fullFields)
                    .map(fieldName -> ClassUtil.getField(tClass, fieldName))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());

            return createTree(list, rootValue, splitChar, fullFieldSet);
        }

        return new ConcurrentSkipListSet<>();
    }

    public static Set<TreeBase> createTree(Collection<? extends TreeBase> list, String rootValue) {
        return createTree(list, rootValue, Constant.RegularAbout.SPOT);
    }

    public static Set<TreeBase> createTreeByNodeIds(Collection<? extends TreeBase> all, Collection<String> ids, String rootValue) {
        return createTreeByNodeIds(all, ids, rootValue, Constant.AgileAbout.DIC_SPLIT);
    }

    /**
     * 创建跟主键相关的树形结构，一般用于数据权限的使用场景
     *
     * @param all        完整树的全部数据
     * @param ids        想要查询相关节点的主键集合
     * @param rootValue  跟节点值
     * @param splitChar  分隔符
     * @param fullFields 累加字段
     * @return 树
     */
    public static Set<TreeBase> createTreeByNodeIds(Collection<? extends TreeBase> all, Collection<String> ids, String rootValue, String splitChar, String... fullFields) {
        if (CollectionUtils.isEmpty(ids)) {
            all.clear();
            return Sets.newTreeSet();
        } else {
            List<TreeBaseProxy> allWrapper = all.stream().map(TreeBaseProxy::new).collect(Collectors.toList());
            createTree(allWrapper, rootValue, splitChar, "fullId");

            Set<String> relevantNode = allWrapper
                    .stream()
                    .filter(m -> ids.contains(m.getId())).flatMap(m -> Arrays.stream(StringUtils.split(m.getFullId(), splitChar))).collect(Collectors.toSet());
            allWrapper.removeIf(m -> !relevantNode.contains(String.valueOf(m.getId())));
            List<TreeBase> allNodes = allWrapper.stream().map(TreeBaseProxy::getTreeBase).collect(Collectors.toList());
            return createTree(allNodes, rootValue, splitChar, fullFields);
        }
    }

    public static class TreeBaseProxy extends TreeBase {
        private final TreeBase treeBase;

        public TreeBaseProxy(TreeBase treeBase) {
            this.treeBase = treeBase;
            this.fullId = String.valueOf(getId());
        }

        @Override
        public String getId() {
            return treeBase.getId();
        }

        @Override
        public String getParentId() {
            return treeBase.getParentId();
        }

        @Override
        public Integer getSort() {
            return treeBase.getSort();
        }

        private String fullId;

        public String getFullId() {
            return fullId;
        }

        public void setFullId(String fullId) {
            this.fullId = fullId;
        }

        public TreeBase getTreeBase() {
            return treeBase;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof TreeBaseProxy)) return false;
            if (!super.equals(o)) return false;
            TreeBaseProxy that = (TreeBaseProxy) o;
            return Objects.equals(treeBase, that.treeBase) && Objects.equals(fullId, that.fullId);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), treeBase, fullId);
        }
    }
}